import logging
import os
import re
import sys
import unittest

import svn.core
import svn.fs

import gvn.userdb

import testing.testcommon

def BuildPathCopyTxn(fs, rev,  path_from, txn_path, author, logmsg, pool):
  """Create a test transaction from files in a directory path.

  Args:
    fs: svn_fs reference
    rev: which revision this transaction is based on
    path_from: where on disk, to mirror a directory tree from
    txn_path: where in the depot to graft those files in, as a transation
    author: who owns that transaction
    logmsg: log message to link to the transaction
    pool: svn_pool reference

  Returns:
    svn txn object

  This duplicates a file tree from path_from (without trailing slash)
  into a transaction inside txn_path (without a trailing slash)
  In many cases, txn_path should likely be empty to add those files at the
  top of the svn tree
  """

  assert (txn_path[-1] != "/" or len(txn_path) == 1), \
                    "trailing slash forbidden for txn_path in BuildPathCopyTxn"
  assert (path_from[-1] != "/" or len(path_from) == 1), \
                    "trailing slash forbidden in path_from BuildPathCopyTxn"

  # Create transaction
  txn = svn.fs.begin_txn(fs, rev, pool)

  # Set basic revprops.
  svn.fs.change_txn_prop(txn, "svn:author", author, pool)
  svn.fs.change_txn_prop(txn, "svn:log", logmsg, pool)

  txnroot = svn.fs.txn_root(txn, pool)

  def txn_mkdir(dir):
    try:
      svn.fs.make_dir(txnroot, dir, pool)
    except svn.core.SubversionException, e:
      if e.apr_err != svn.core.SVN_ERR_FS_ALREADY_EXISTS:
        raise

  # Create all the leading elements of the base path if needed
  def make_base_dirs(dir):
    if dir and dir !='/':
      make_base_dirs(os.path.dirname(dir))
    else:
      return
    txn_mkdir(dir)

  # first, recursively make the base directory where we graft our tree of files
  make_base_dirs(txn_path)
  for base_path, subdirs, files in os.walk(path_from):
    # non regex way to write base_path =~ s/^path_from/txn_path/ -> mk_txn_path
    mk_txn_path = '/'.join([txn_path, base_path[len(path_from)+1:]])
    txn_mkdir(mk_txn_path)
    for txn_file in files:
      txn_file_path = mk_txn_path + "/" + txn_file
      svn.fs.make_file(txnroot, txn_file_path, pool)
      fp = svn.fs.apply_text(txnroot, txn_file_path, None, pool)

      txn_file = open(base_path + "/" + txn_file)
      for line in txn_file:
        svn.core.svn_stream_write(fp, line)
      txn_file.close()
      svn.core.svn_stream_close(fp)

  # We've built our transaction, we can close/finalize it now that it's ready
  svn.fs.close_root(txnroot)
  return txn


def BuildPropTxn(fs, rev, prop_name, prop_val, path, author, logmsg, pool):
  """Create a test transaction with a file attribute on an existing path.

  Args:
    fs: svn_fs reference
    rev: which revision this transaction is based on
    prop_name: which property to create
    prop_val: which property value to set
    path: which file or directory to attach the property to, or "REV" for
          rev property
    author: who owns that transaction
    logmsg: log message to link to the transaction
    pool: svn_pool reference

  Returns:
    svn txn object

  This adds the attaches prop_name: prop_value to path.
  txn_path is required to point to an existing file or directory
  """

  assert (path == "REV" or path == "" or path[-1] != "/" or len(path) == 1), \
                            "trailing slash forbidden for path in BuildPropTxn"

  # Create transaction
  txn = svn.fs.begin_txn(fs, rev, pool)

  # Set basic revprops.
  svn.fs.change_txn_prop(txn, "svn:author", author, pool)
  svn.fs.change_txn_prop(txn, "svn:log", logmsg, pool)

  txnroot = svn.fs.txn_root(txn, pool)

  if path == "REV":
    svn.fs.change_txn_prop(txn, prop_name, prop_val, pool)
  else:
    svn.fs.change_node_prop(txnroot, path, prop_name, prop_val, pool)

  # We've built our transaction, we can close/finalize it now that it's ready
  svn.fs.close_root(txnroot)
  return txn


SVN_USERDB_PATH = "userdb"
class HookTestCase(unittest.TestCase):
  def setUp(self):
    """create temp repo, test group file, and logger object.
    """

    # open the rest repository stored in testdata/dumpfile
    self.repo_name = testing.testcommon.NewTmpRepository()
    # testcommon already cd's us in /tmp/tmpxxxxx under which we have
    # the testdata subdir

    # Setup logging object used by the hook
    # Hook debugging/tracing is done with export GVN_TEST_DEBUG=y before running
    if "GVN_TEST_DEBUG" in os.environ:
      level = logging.DEBUG
    else:
      level = logging.CRITICAL # This is equivalent to turning it off
    self._logger = logging.getLogger("test_check_owners")
    self._logger.setLevel(level)
    self._term_handler = logging.StreamHandler(sys.stderr)
    self._term_handler.setFormatter(logging.Formatter("%(message)s"))
    self._logger.addHandler(self._term_handler)

    self.userdb = gvn.userdb.UserDB(SVN_USERDB_PATH, create_db=True)


  def tearDown(self):
    """remove logging handler.
    """
    # apparently, the handler survives this object going out of scope, so you
    # have to manually remove it -- merlin
    # epg says this sucks :)
    self._logger.removeHandler(self._term_handler)

  def assertHookResult(self, actual_result, expected_result):
    """Looks for expected_result (regex|int) in actual_result.
    """
    self.assert_(type(actual_result) == type(expected_result),
                  "expected '%s' but got '%s'" % (expected_result,
                                                  actual_result))
    if isinstance(expected_result, int):
      self.assertEqual(actual_result, expected_result)
    else:
      if expected_result == actual_result:
        return True;
      else:
        self.assert_(re.search(expected_result, actual_result),
                     "expected '%s' but got '%s'" % (expected_result,
                                                     actual_result))
